package com.jifujun.junbatis.base.service;


import com.jifujun.junbatis.base.dao.GenericDao;
import com.jifujun.junbatis.base.exception.BusinessException;
import com.jifujun.junbatis.base.exception.CatchException;
import com.jifujun.junbatis.base.exception.FrameException;
import com.jifujun.junbatis.base.util.ArgIndex;
import com.jifujun.junbatis.base.util.SqlStringUtil;
import com.jifujun.junbatis.base.util.SqlUtil;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;

/**
 * Create with IntelliJ IDEA
 * Project Name: junbatis
 * Package Name: club.emergency.junbatis.base
 * Date: 2018/3/30 14:35
 * User: 快乐的小菌菌
 * Description:查询构造器，这个类里的容器我都是在需要时的候才去创建，不建议直接在域里直接new。
 */
@Slf4j
@Getter(value = AccessLevel.PROTECTED)
public class ConditionConstructor {
    private Map<String, Object> selectConditionContainer;
    private List<String> selectConditionComplete;
    private Map<String, Object> orConditionContainer;
    private List<String> orConditionComplete;
    private Map<String, Object> insertMoreContainer;
    private List<String> insertMoreComplete;
    private Map<String, Object> updateConditionContainer;
    private List<String> updateConditionComplete;
    private Map<String, Object> updateValueContainer;
    private List<String> updateValueComplete;
    private Map<String, Object> deleteConditionContainer;
    private List<String> deleteConditionComplete;
    private String[] orderByArgs;
    private Object updateConditionObject;

    /**
     * and条件构建
     * 根据{@link GenericDao#selectInPageForOne(String[], Map, String[], Map, Integer, Integer, String[], Boolean, Class)}中的参数顺序来决定ArgIndex参数。
     *
     * @param value     参数值
     * @param condition 查询条件
     * @param column    字段名称，可以是驼峰模式，也可以不是
     * @return this
     */
    public ConditionConstructor conditionAnd(Object value, @NonNull Condition condition, @NonNull String column) {
        if (condition != null && column != null) {
            if (value == null || value.toString().indexOf("null") == 1)
                value = "";
            if (Objects.isNull(this.selectConditionContainer))
                this.selectConditionContainer = new HashMap<>();
            if (Objects.isNull(this.selectConditionComplete))
                this.selectConditionComplete = new ArrayList<>();
            int size = this.selectConditionContainer.size();
            //传""相当于没传参数
            if (!"".equals(value)) {
                this.selectConditionContainer.put(ArgIndex.ARG_C + size, value);
                String conditionString = SqlUtil.constructorCondition(condition, column, size, true, ArgIndex.ARG_1, ArgIndex.ARG_C);
                this.selectConditionComplete.add(conditionString);
            }
            return this;
        } else {
            throw new CatchException(BusinessException.CONDITION_ISNULL);
        }
    }

    /**
     * or条件构建 和{@link ConditionConstructor#conditionAnd(Object, Condition, String)}用法一样。
     * <p>
     * 可以被多次调用，但在一行中多次调用或生成不稳定的SQL。
     *
     * @param value     参数值
     * @param condition 查询条件
     * @param column    字段名称，可以是驼峰模式，也可以不是
     * @return this
     */
    public ConditionConstructor conditionOr(Object value, @NonNull Condition condition, @NonNull String column) {
        if (condition != null && column != null) {
            if (value == null || value.toString().indexOf("null") == 1)
                value = "";
            if (Objects.isNull(this.orConditionContainer))
                this.orConditionContainer = new HashMap<>();
            if (Objects.isNull(this.orConditionComplete))
                this.orConditionComplete = new ArrayList<>();
            int size = this.orConditionContainer.size();
            //传""相当于没传参数
            if (!"".equals(value)) {
                this.orConditionContainer.put(ArgIndex.ARG_R + size, value);
                String conditionString = SqlUtil.constructorCondition(condition, column, size, true, ArgIndex.ARG_3, ArgIndex.ARG_R);
                this.orConditionComplete.add(conditionString);
            }
            return this;
        } else {
            throw new CatchException(BusinessException.CONDITION_ISNULL);
        }
    }

    /**
     * update条件构建 和{@link ConditionConstructor#conditionAnd(Object, Condition, String)}用法一样。
     *
     * @param value     参数值
     * @param condition 查询条件
     * @param column    字段名称，可以是驼峰模式，也可以不是
     * @return this
     */
    public ConditionConstructor conditionUpdate(Object value, @NonNull Condition condition, @NonNull String column) {
        if (condition != null && column != null) {
            if (value == null || value.toString().indexOf("null") == 1)
                value = "";
            if (Objects.isNull(this.updateConditionContainer))
                this.updateConditionContainer = new HashMap<>();
            if (Objects.isNull(this.updateConditionComplete))
                this.updateConditionComplete = new ArrayList<>();
            int size = this.updateConditionContainer.size();
            this.updateConditionContainer.put(ArgIndex.ARG_C + size, value);
            String conditionString = SqlUtil.constructorCondition(condition, column, size, false, ArgIndex.ARG_1, ArgIndex.ARG_C);
            this.updateConditionComplete.add(conditionString);
            return this;
        } else {
            throw new CatchException(BusinessException.CONDITION_ISNULL);
        }
    }

    /**
     * delete条件构建 和{@link ConditionConstructor#conditionAnd(Object, Condition, String)}用法一样。
     *
     * @param value     参数值
     * @param condition 查询条件
     * @param column    字段名称，可以是驼峰模式，也可以不是
     * @return this
     */
    public ConditionConstructor conditionDelete(Object value, @NonNull Condition condition, @NonNull String column) {
        if (condition != null && column != null) {
            if (value == null || value.toString().indexOf("null") == 1)
                value = "";
            if (Objects.isNull(this.deleteConditionContainer))
                this.deleteConditionContainer = new HashMap<>();
            if (Objects.isNull(this.deleteConditionComplete))
                this.deleteConditionComplete = new ArrayList<>();
            int size = this.deleteConditionContainer.size();
            this.deleteConditionContainer.put(ArgIndex.ARG_D + size, value);
            String conditionString = SqlUtil.constructorCondition(condition, column, size, false, ArgIndex.ARG_1, ArgIndex.ARG_D);
            this.deleteConditionComplete.add(conditionString);
            return this;
        } else {
            throw new CatchException(BusinessException.CONDITION_ISNULL);
        }
    }

    /**
     * 通过List传入实现批量insert
     * 要求List中必须是同一种数据类型。
     * 把实际的参数放到map里，在list中构建mybatis识别的语法
     * 判断属性子类里面没有去父类里面找的逻辑我放在了catch....里。因为在子类里面没有的话强行找会抛异常，所以直接在异常里写了。
     * 要是还没有的话，就会throw异常
     *
     * @param list 需要新增的list
     */
    public void insertMoreConstructor(@NonNull List<?> list) {
        this.insertMoreContainer = new HashMap<>();
        this.insertMoreComplete = new ArrayList<>();
        if (list.size() != 0) {
            Class<?> clazz = list.get(0).getClass();
            for (Object object : list) {
                String[] strings = SqlUtil.sqlColumnBuilder(clazz, false, true);
                for (String upperColumn : strings) {
                    int size = this.insertMoreContainer.size();
                    try {
                        String lowerColumn = SqlStringUtil.firstCharLowerCase(upperColumn);
                        Field childField = clazz.getDeclaredField(lowerColumn);
                        childField.setAccessible(true);
                        Object childValue = childField.get(object);
                        if (Objects.nonNull(childValue)) {
                            this.insertMoreContainer.put(ArgIndex.ARG_C + size, childValue);
                        } else {
                            //缺少的字段一定要写null。不写会报错。。。
                            this.insertMoreContainer.put(ArgIndex.ARG_C + size, null);
                        }
                        this.insertMoreComplete.add("#{" + ArgIndex.ARG_1 + "." + ArgIndex.ARG_C + size + "}");
                    } catch (NoSuchFieldException e) {
                        Class<?> superclass = clazz.getSuperclass();
                        try {
                            Field superField = superclass.getDeclaredField(upperColumn);
                            superField.setAccessible(true);
                            if (upperColumn.equals("usingType"))
                                superField.set(object, 1);
                            if (upperColumn.equals("createTime") || upperColumn.equals("modifyTime"))
                                superField.set(object, new Date());
                            Object superValue = superField.get(object);
                            if (Objects.nonNull(superValue)) {
                                this.insertMoreContainer.put(ArgIndex.ARG_C + size, superValue);
                            } else {
                                //缺少的字段一定要写null。不写会报错。。。
                                this.insertMoreContainer.put(ArgIndex.ARG_C + size, null);
                            }
                            this.insertMoreComplete.add("#{" + ArgIndex.ARG_1 + "." + ArgIndex.ARG_C + size + "}");
                        } catch (NoSuchFieldException | IllegalAccessException e1) {
                            e1.printStackTrace();
                            throw new CatchException(FrameException.INSERT_MORE_CONSTRUCTOR_FAILED);
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                        throw new CatchException(FrameException.INSERT_MORE_CONSTRUCTOR_FAILED);
                    }
                }
            }
        } else {
            throw new CatchException(BusinessException.INSERT_MORE_LIST_SIZE_ZERO);
        }
    }

    /**
     * 转换成mybatis可识别的语法，这个方法我实现起来相当麻烦，归根结底就是处理字符串，大致分为两过程实现。
     * 第一个过程时在{@link SqlStringUtil#parenthesisHandler(List, Class)}中进行的，详情请移步。
     * 第二块是在本方法中进行的。把数组中的数据"分门别类"，处理步骤是先把数组两头处理好(数组第一个和最后一个)，之后在处理中间的。
     * 总之处理结果的样子类似为：#{arg1.C0}, #{arg1.C1}), (#{arg1.C2}, #{arg1.C3}), (#{arg1.C4}, #{arg1.C5}
     * 注意两端是没有括号的呦。
     * ps：如果您看这个方法过于复杂并且有怀疑其效率问题的话，我本人觉得我在工作中一般用动态SQL进行数据库insert动作的时候，都是放在
     * for()中！！！，但是我这里实现的仅仅是一次字符串处理，一次与数据库链接，在数据量大的时候可以能明显的减少数据库的连接次数，间接的可以提高数据库和服务器的稳定性，所以我觉得我写的这几个方法的工作量还是有他的价值，经过这样的思考之后我还是决定把这个批量insert方法完成。可能我个人能力有限，这个方法只能封装到这个程度，不过也已经可以解决使用的问题了。其中的数据分块是根据每个POJO的字段数来划分的。想看细节实现可以在测试类中debug一下就可以看明白了。
     * 已经把@IgnoreColumn注解集成进来了。可以忽略不想insert的属性。使用方法和往常一样(一般放get方法上)。
     * <p>
     * 这个方法目前只支持单个表批量插入，级联插入还没有做测试
     *
     * @param clazz {@link GenericServiceImpl#insertMore(ConditionConstructor)}
     *              入参，为了获取到这个Class中一共有几个参数，方便划分。
     * @return String[] 数组
     */
    String[] insertMoreCompleteToArray(@NonNull Class<?> clazz) {
        List<String> insertMoreCompleteList = SqlStringUtil.parenthesisHandler(this.insertMoreComplete, clazz);
        String[] insertMoreCompletes = new String[insertMoreCompleteList.size()];
        insertMoreCompleteList.toArray(insertMoreCompletes);
        int length = insertMoreCompletes.length;
        for (int i = 0; i < length; i++) {
            //这层是找数组里第一个和最后一个做处理
            if (i == 0) {
                insertMoreCompletes[i] = "#{" + ArgIndex.ARG_1 + "." + insertMoreCompletes[i] + "}";
            } else if (i == length - 1) {
                insertMoreCompletes[i] = "#{" + ArgIndex.ARG_1 + "." + insertMoreCompletes[length - 1] + "}";
            } else {
                //这层是找数组中的
                if (insertMoreCompletes[i].startsWith("(")) {
                    insertMoreCompletes[i] = "(#{" + ArgIndex.ARG_1 + "." + insertMoreCompletes[i].substring(1) + "}";
                } else if (insertMoreCompletes[i].endsWith(")")) {
                    insertMoreCompletes[i] = "#{" + ArgIndex.ARG_1 + "." + insertMoreCompletes[i].substring(0, insertMoreCompletes[i].indexOf(")")) + "})";
                } else {
                    insertMoreCompletes[i] = "#{" + ArgIndex.ARG_1 + "." + insertMoreCompletes[i] + "}";
                }
            }
        }
        return insertMoreCompletes;
    }

    /**
     * 在按条件更新的时候用来存放对象的。当且仅当在调用{@link ConditionConstructor#conditionUpdate(Object, Condition, String)}时候改方法才有意义。
     *
     * @param updateConditionObject 传入的更新数据的对象。
     */
    public void setUpdateConditionObject(Object updateConditionObject) {
        this.updateConditionObject = updateConditionObject;
    }

    /**
     * 构建的条件封装成mybatis可识别的格式——删除
     * 更新需要找到父类里面的属性，只排除id属性，剩下的也都会更新。
     * 判断属性子类里面没有去父类里面找的逻辑我放在了catch....里。因为在子类里面没有的话强行找会抛异常，所以直接在异常里写了。
     * 要是还没有的话，就会throw异常
     *
     * @return String[]
     */
    Map<String, Object> getUpdateConditionObjectMap() {
        if (Objects.nonNull(this.updateConditionObject)) {
            this.updateValueContainer = new HashMap<>();
            this.updateValueComplete = new ArrayList<>();
            Class clazz = this.updateConditionObject.getClass();
            Object obj = this.updateConditionObject;
            String[] strings = SqlUtil.updateColumnBuilder(obj, clazz, false, true);
            for (String column : strings) {
                if (!column.equals("id")) {
                    int size = this.updateValueContainer.size();
                    try {
                        Field childField;
                        childField = clazz.getDeclaredField(SqlStringUtil.underlineHump(column));
                        String childFieldName = childField.getName();
                        childField.setAccessible(true);
                        if (Objects.nonNull(childField.get(obj))) {
                            this.updateValueContainer.put(ArgIndex.ARG_S + size, childField.get(obj));
                            this.updateValueComplete.add(SqlStringUtil.humpUnderline(childFieldName) + "=#{" + ArgIndex.ARG_2 + "." + ArgIndex.ARG_S + size + "}");
                        }
                    } catch (NoSuchFieldException e) {
                        Class superclass = clazz.getSuperclass();
                        Field superField;
                        try {
                            superField = superclass.getDeclaredField(SqlStringUtil.underlineHump(column));
                            superField.setAccessible(true);
                            String superFieldName = superField.getName();
                            //更新modifyTime，更新的时候只需要更新modifyTime。
                            if (superFieldName.equals("modifyTime")) {
                                superField.set(obj, new Date());
                                if (Objects.nonNull(superField.get(obj))) {
                                    this.updateValueContainer.put(ArgIndex.ARG_S + size, superField.get(obj));
                                    this.updateValueComplete.add(SqlStringUtil.humpUnderline(superFieldName) + "=#{" + ArgIndex.ARG_2 + "." + ArgIndex.ARG_S + size + "}");
                                }
                            }
                        } catch (NoSuchFieldException e1) {
                            e1.printStackTrace();
                            throw new CatchException(FrameException.UPDATE_CONDITION_OBJECT_MAP);
                        } catch (IllegalAccessException e1) {
                            e1.printStackTrace();
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            throw new CatchException(BusinessException.UPDATE_CONDITION_OBJECT_ISNULL);
        }
        return this.updateValueContainer;
    }

    /**
     * 构建的条件封装成mybatis可识别的格式——selectInPageForOne
     *
     * @return String[]
     */
    String[] selectConditionCompleteToArray() {
        if (Objects.nonNull(this.selectConditionComplete)) {
            String[] selectConditionCompletes = new String[this.selectConditionComplete.size()];
            return this.selectConditionComplete.toArray(selectConditionCompletes);
        } else
            return null;
    }

    /**
     * 构建的条件封装成mybatis可识别的格式——or
     *
     * @return String[]
     */
    String[] orConditionCompleteToArray() {
        if (Objects.nonNull(this.orConditionComplete)) {
            String[] orConditionCompletes = new String[this.orConditionComplete.size()];
            return this.orConditionComplete.toArray(orConditionCompletes);
        } else
            return null;
    }

    /**
     * 构建的条件封装成mybatis可识别的格式——update
     *
     * @return String[]
     */
    String[] updateConditionCompleteToArray() {
        String[] updateConditionCompletes = new String[this.updateConditionComplete.size()];
        return this.updateConditionComplete.toArray(updateConditionCompletes);
    }

    /**
     * 构建的条件封装成mybatis可识别的格式——update_object_value
     *
     * @return String[]
     */
    String[] updateValueCompleteToArray() {
        String[] updateValueCompletes = new String[this.updateValueComplete.size()];
        return this.updateValueComplete.toArray(updateValueCompletes);
    }

    /**
     * 构建的条件封装成mybatis可识别的格式——delete
     *
     * @return String[]
     */
    String[] deleteConditionCompleteToArray() {
        String[] deleteConditionCompletes = new String[this.deleteConditionComplete.size()];
        return this.deleteConditionComplete.toArray(deleteConditionCompletes);
    }

    /**
     * 参数数组——group by
     * <p>
     * String[] 排序条件构造
     */
    public void setOrderByDesc(String... columns) {
        if (columns.length != 0) {
            if (Objects.isNull(this.orderByArgs))
                this.orderByArgs = new String[columns.length];
            for (int i = 0; i < columns.length; i++) {
                this.orderByArgs[i] = columns[i] + " desc";
            }
        }
    }
}
